#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <assert.h>
#include <errno.h>
#include <dirent.h>

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h>

// main函数的第二个参数是一个字符指针数组，数组当中的第一个字符指针存储的是可执行程序的位置，
//      其余字符指针存储的是所给的若干选项，最后一个字符指针为空，
//      而main函数的第一个参数代表的就是字符指针数组当中的有效元素个数

void test_argv(int argc,char *argv[]) {

    // for(int i=0;i<argc;++i) {
    //     printf("%d : %s\n",i,argv[i]);
    // }
    
    int i=0;
    while(argv[i]!=NULL) {
        printf("%d : %s\n",i,argv[i]);
        i++;
    }

    if(argc > 1)
	{
		if(strcmp(argv[1], "-a") == 0)
		{
			 printf("you used -a option...\n");
		}
		else if(strcmp(argv[1], "-b") == 0)
		{
			printf("you used -b option...\n");
		}
		else
		{
			printf("you used unrecognizable option...\n");
		}
	}
	else
	{
		printf("you did not use any option...\n");
    }
}

// main函数的第三个参数接收的实际上就是环境变量表，我们可以通过main函数的第三个参数来获取系统的环境变量
// libc中定义的全局变量environ指向环境变量表，environ没有包含在任何头文件中，所以在使用时要用extern进行声明
extern char ** environ;
void test_env(char **env) {

    // for(int i=0;env[i]!=NULL;++i) {
    //     printf("%d : %s\n",i,env[i]);
    // }

    // 除了使用main函数的第三个参数来获取环境变量以外，我们还可以通过第三方变量environ来获取
    int i=0;
    while(environ[i]) {
        printf("%d : %s\n",i,environ[i]);
        i++;
    }
}

int g_val=99;
int g_unval;
void test_addresses(int argc,char *argv[],char *env[]) {
    // 代码区
    printf("code addr: %p\n",test_addresses);
    // 只读常量区
    const char* pstr="hello basicIO";
    printf("read only addr: %p\n",pstr);
    // 已初始化数据区
    printf("init addr: %p\n",&g_val);
    // 未初始化数据区
    printf("uninit addr: %p\n",&g_unval);
    // 堆
    int *ptr=(int *)malloc(sizeof(int));
    printf("heap addr: %p\n",ptr);
    // 栈
    printf("stack addr: %p\n",&ptr);
    // 命令行参数
    printf("args addr: %p\n",argv[0]);
    // 环境变量
    printf("env addr: %p\n",env[0]);
}

// cwd与exe的区别 
// lrwxrwxrwx 1 yx yx 0 May 17 21:08 cwd -> /home/yx/LinuxCode/BasicIO
// lrwxrwxrwx 1 yx yx 0 May 17 21:08 exe -> /home/yx/LinuxCode/BasicIO/basic_io
// 在这里我们可以看到两个软链接文件cwd和exe，cwd就是进程运行时我们所处的路径，而exe就是该可执行程序的所处路径
void test_proc() {

    printf("pid: %d\n",getpid());
    // 我们这里所说的当前路径不是指可执行程序所处的路径，而是指该可执行程序运行成为进程时我们所处的路径
    // 所以如果log.txt在当前路径不存在,则在当前路径创建也即可执行程序所处的路径创建log.txt
    FILE *pf=fopen("log.txt","w");
    assert(pf!=NULL);

    while(true) {

    }

}

// 默认三个流
// 任何进程在运行的时候都会默认打开三个输入输出流，即标准输入流、标准输出流以及标准错误流，对应到C语言当中就是stdin、stdout以及stderr
// 其中，标准输入流对应的设备就是键盘，标准输出流和标准错误流对应的设备都是显示器
/*
    extern FILE *stdin; fd: 0
    extern FILE *stdout; fd: 1
    extern FILE *stderr; fd: 2
*/
void test_stream() {

    // 若想创建出来文件的权限值不受umask的影响，则需要在创建文件前使用umask函数将文件默认掩码设置为0
    // mode_t umask(mode_t mask);
    // return mode_t: 这个系统调用成功则返回前一个掩码的值
    umask(0);

    //int open(const char *pathname, int flags, mode_t mode);
    // O_RDONLY:以只读的方式打开文件
    // O_WRNOLY:以只写的方式打开文件
    // O_APPEND:以追加的方式打开文件
    // O_RDWR:以读写方式打开文件
    // O_CREAT:当目标文件不存在时，创建文件

    // open函数的返回值是新打开文件的文件描述符:
    // 实际上这里所谓的文件描述符本质上是一个指针数组的下标，指针数组当中的每一个指针都指向一个被打开文件的文件信息，
    // 通过对应文件的文件描述符就可以找到对应的文件信息。当使用open函数打开文件成功时数组当中的指针个数增加，
    // 然后将该指针在数组当中的下标进行返回，而当文件打开失败时直接返回-1，因此，成功打开多个文件时所获得的文件描述符就是连续且递增的。
    // 而Linux进程默认情况下会有3个缺省打开的文件描述符，分别就是标准输入0、标准输出1、标准错误2，
    // 这就是为什么成功打开文件时所得到的文件描述符是从3开始进程分配的

    // int fd=open("log.txt",O_WRONLY|O_CREAT,0664); // 只写
    int fd=open("log.txt",O_RDWR|O_CREAT|O_APPEND,0664); // 读写方式打开

    if(-1==fd) {
        perror("open");
        exit(1);
    }
    
    // ssize_t write(int fd, const void *buf, size_t count);
    // return ssize_t 如果数据写入成功，实际写入数据的字节个数被返回,如果数据写入失败，-1被返回
    // fd: 向指定文件中写入数据
    char buffer[1024];
    for(int i=0;i<5;++i) {

        sprintf(buffer,"data %d ",i);
        // 向log.txt写入一些数据
        ssize_t n=write(fd,buffer,strlen(buffer));

        printf("return ssize_t: %d\n",n);
    }

    sleep(5);

    // ssize_t read(int fd, void *buf, size_t count);
    // 从指定文件中读取数据

    // 将刚才写入log.txt的数据读取上来
    ssize_t rn=read(fd,buffer,sizeof(buffer)-1);
    assert(rn>=0);
    buffer[rn]='\0';

    printf("rn %d buffer %s\n",rn,buffer);

    // 使用close函数时传入需要关闭文件的文件描述符即可，若关闭文件成功则返回0，若关闭文件失败则返回-1。
    // int close(int fd);
    close(fd);
}

void test_read() {

    int fd=open("log.txt",O_RDWR|O_CREAT,0664); // 读写方式打开
    if(-1==fd) {
        perror("open");
        exit(1);
    }

    char buffer[1024];

    ssize_t rn=read(fd,buffer,sizeof(buffer)-1);
    assert(rn>=0);

    buffer[rn]='\0';
    printf("rn %d buffer %s\n",rn,buffer);

    close(fd);
}

// 0就是标准输入流，对应键盘；1就是标准输出流，对应显示器；2就是标准错误流，也是对应显示器。
// 而键盘和显示器都属于硬件，属于硬件就意味着操作系统能够识别到，当某一进程创建时，
// 操作系统就会根据键盘、显示器、显示器形成各自的struct file，将这3个struct file连入文件双链表当中，
// 并将这3个struct file的地址分别填入fd_array数组下标为0、1、2的位置，至此就默认打开了标准输入流、标准输出流和标准错误流

void test_rule_for_distribution() {

    // 可以看到这五个文件获取到的文件描述符都是从3开始连续递增的，这很好理解，
    // 因为文件描述符本质就是数组的下标，而当进程创建时就默认打开了标准输入流、标准输出流
    // 和标准错误流，也就是说数组当中下标为0、1、2的位置已经被占用了，所以只能从3开始进行分配。
	// int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666);
	// int fd2 = open("log2.txt", O_RDONLY | O_CREAT, 0666);
	// int fd3 = open("log3.txt", O_RDONLY | O_CREAT, 0666);
	// int fd4 = open("log4.txt", O_RDONLY | O_CREAT, 0666);
	// int fd5 = open("log5.txt", O_RDONLY | O_CREAT, 0666);
	// printf("fd1:%d\n", fd1);
	// printf("fd2:%d\n", fd2);
	// printf("fd3:%d\n", fd3);
	// printf("fd4:%d\n", fd4);
	// printf("fd5:%d\n", fd5);
    /*
        fd1:3
        fd2:4
        fd3:5
        fd4:6
        fd5:7
    */

    // 先关闭0和2
    close(0);
    close(2);

    int fd1 = open("log1.txt", O_RDONLY | O_CREAT, 0666);
	int fd2 = open("log2.txt", O_RDONLY | O_CREAT, 0666);
	int fd3 = open("log3.txt", O_RDONLY | O_CREAT, 0666);
	int fd4 = open("log4.txt", O_RDONLY | O_CREAT, 0666);
	int fd5 = open("log5.txt", O_RDONLY | O_CREAT, 0666);
	printf("fd1:%d\n", fd1);
	printf("fd2:%d\n", fd2);
	printf("fd3:%d\n", fd3);
	printf("fd4:%d\n", fd4);
	printf("fd5:%d\n", fd5);
    // 文件描述符是从最小但是没有被使用的fd_array数组下标开始进行分配的。
    /*
        fd1:0
        fd2:2
        fd3:3
        fd4:4
        fd5:5
    */
}

// 实际上我们使用重定向时，重定向的是文件描述符是1的标准输出流，而并不会对文件描述符是2的标准错误流进行重定向
void test_redirected() {

    // 输入重定向:输入重定向就是，将我们本应该从一个文件读取数据，现在重定向为从另一个文件读取数据。
    close(0);
    // 如果我们想让本应该从“键盘文件”读取数据的scanf函数，改为从log.txt文件当中读取数据，
    //      那么我们可以在打开log.txt文件之前将文件描述符为0的文件关闭，
    //      也就是将“键盘文件”关闭，这样一来，当我们后续打开log.txt文件时所分配到的文件描述符就是0。

    int fd3=open("log.txt",O_RDONLY|O_CREAT,0666);

    char strs[30];

    // stdin指向的FILE结构体中存储的文件描述符是0，因此fscanf实际上就是向文件描述符为0的文件读取数据
    while(fscanf(stdin,"%s",strs)!=EOF) {
        printf(strs);
    }
    /*
        [yx@VM-12-12-centos BasicIO]$ ./basic_io 
        helloprintffprintfhellofputshelloprintffprintfhellofputs
    */
    close(fd3);
    return;

    // 追加重定向:追加重定向和输出重定向的唯一区别就是，输出重定向是覆盖式输出数据，而追加重定向是追加式输出数据。
    close(1);

    int fd2=open("log.txt",O_WRONLY|O_APPEND|O_CREAT,0666);
    if(fd2<0) {
        perror("open");
    }

    printf("hello printf\n");
    fprintf(stdout,"fprintf\n");
    fputs("hello fputs\n",stdout);

    fflush(stdout);

    close(fd2);
    /*
        [yx@VM-12-12-centos BasicIO]$ ./basic_io 
        [yx@VM-12-12-centos BasicIO]$ cat log.txt 
        hello printf
        fprintf
        hello fputs
        hello printf
        fprintf
        hello fputs
    */

    return;

    //输出重定向:输出重定向就是，将我们本应该输出到一个文件的数据重定向输出到另一个文件中
    // 如果我们想让本应该输出到“显示器文件”的数据输出到log.txt文件当中，
    //      那么我们可以在打开log.txt文件之前将文件描述符为1的文件关闭，也就是将“显示器文件”关闭，
    //      这样一来，当我们后续打开log.txt文件时所分配到的文件描述符就是1

    close(1);

    int fd1=open("log.txt",O_WRONLY|O_CREAT,0666);
    if(fd1<0) {
        perror("open");
    }

    printf("hello printf\n");
    fprintf(stdout,"fprintf\n");
    fputs("hello fputs\n",stdout);

    fflush(stdout);

    close(fd1);
    /*
    [yx@VM-12-12-centos BasicIO]$ cat log.txt 
    hello printf
    fprintf
    hello fputs
    */
}

void test_dup2() {

    // int dup2(int oldfd, int newfd);
    // dup2会将fd_array[oldfd]的内容拷贝到fd_array[newfd]当中，
    //    如果有必要的话我们需要先使用关闭文件描述符为newfd的文件。
    
    int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT, 0666);
    // O_WRONLY: 从文件的开头覆盖写
    // O_WRONLY+O_APPEND: 追加写
    // O_TRUNC: 会清空文件
	if (fd < 0){
		perror("open");
		exit(1);
	}
	close(1);
	dup2(fd, 1);
	printf("hello printf\n");
	fprintf(stdout, "hello fprintf\n");

    // dup2如果调用成功，返回newfd，否则返回-1。
}

// 了解标准库函数与系统调用的关系
/*
fopen (3):
    调用open(2)打开指定的文件，返回一个文件描述符(就是一个int类型的编号)，分配一个FILE结构体，
    其中包含该文件的描述符、I/0缓 冲区和当前读写位置等信息，返回这个F ILE结构体的地址。
*/

/*
fgetc (3):
    通过传入的FILE*参数找到该文件的描述符、I/0缓冲区和当前读写位置，
    判断能否从I/0缓冲区中读到下一个字符，如果能读到就直接返回该字符，
    否则调用read(2)，把文件描述符传进去，让内核读取该文件的数据到I/0缓冲区，然后返回下一个字符。
*/

/*
fputc (3):
    判断该文件的I/0缓冲区是否有空间再存放一个字符，如果有空间则直接保存在I/0缓冲区中并返回，
    如果I/0缓冲区已满就调用wr ite(2)，让内核把I/0缓冲区的内容写回文件。
*/

/*
fclose (3):
    如果I/0缓冲区中还有数据没写回文件就调用wr ite (2)写回文件，
    然后调用close(2)关闭文件，释放F ILE结构体和1/0缓冲区。
*/

// 三类缓冲机制
//  1. 全缓冲 : 只有缓冲区满了才进行io
//  2. 行缓冲 : 遇到\n 或者 缓冲区满了才进行io
//  3. 无缓冲 : 无条件io,
void test_buffer() {

    // 验证当前环境下stdout对应缓冲区的大小
    // 一直到1024不会打印,到了1025就会打印
    for(int i=0;i<1025;++i) {
        fputc('c',stdout);
    }

    while(true) ;

    return;
    // 而stderr是无缓冲
    fprintf(stderr,"A");
    fprintf(stderr,"B");
    fprintf(stderr,"C");
    fprintf(stderr,"\n");
    fprintf(stderr,"x");
    fprintf(stderr,"y");
    fprintf(stderr,"z");
    /*
        [yx@VM-12-12-centos BasicIO]$ ./basic_io 
        ABC
        xyz
    */
    while(true) {

    }

    return;
    // stdout是典型的行缓冲
    fprintf(stdout,"A");
    fprintf(stdout,"B");
    fprintf(stdout,"C");
    fprintf(stdout,"\n");
    fprintf(stdout,"x");
    fprintf(stdout,"y");
    fprintf(stdout,"z");
    /*
        [yx@VM-12-12-centos BasicIO]$ ./basic_io 
        ABC
    */
    while(true) {

    }

}

/*
阻塞(Block):
    1. 当进程调用一个阻塞的系统函数时，该进程被置于睡眠(sleep)状态，这时内核调度其它进程运行，
        直到该进程等待的事件发生了(比如网络上接收到数据包，或者调用sleep指定的睡眠时间到了)它才有可能继续运行。
    2. 与睡眠状态相对的是运行(Running)状态，在Linux内核中，处于运行状态的进程分为两种情况:
        正在被调度执行: CPU处于该进程的上下文环境中，程序计数器(eip)里保存着该进程
                        的指令地址，正在执行该进程的指令，正在读写该进程的地址空间。
        就绪状态:该进程不需要等待什么事件发生，随时都可以执行，但CPU暂时还在执行另一个进程，
                        所以该进程在一个就绪队列中等待被内核调度。
*/

// /dev/tty 终端设备(字符文件) : crw-rw-rw- 1 root tty 5(主设备号), 0(次设备号) Jul  3 15:24 /dev/tty
void test_block() {

    // 阻塞式读写
    int fd=open("/dev/tty",O_RDWR);
    if(fd<0) {
        perror("open");
        exit(1);
    }
    // 先从终端中读
    char buffer[10];
    ssize_t n=read(fd,buffer,sizeof(buffer)-1);

    printf("read size: %d\n",n); // 打印从终端读到多少字节

    write(fd,buffer,n); // 最后将读端的内容写到终端
}

/*
非阻塞:
    如果在open一个设备时指定了0_NONBLOCK标志，read/write就不会阻塞。以read为例，
    如果设备暂时没有数据可读就返回-1，同时置errno为EWOULDBLOCK (或者EAGAIN,这两个宏定义的值相同)
    表示本来应该阻塞在这里，事实上并没有阻塞而是直接返回错误，调用者应该试着再读一次(again)
    这种行为方式称为轮询(poll) ，调用者只是查询一下，而不是阻塞在这里死等，这样可以同时监视多个设备
*/

void test_nonblock() {
    // 非阻塞式读写
    int fd=open("/dev/tty",O_RDWR | O_NONBLOCK);
    if(fd<0) {
        perror("open");
        exit(1);
    }

    char buffer[128];
    ssize_t n=0;
    // // 轮训检测
    // while(true) {

    //     n=read(fd,buffer,sizeof(buffer)-1);
    //     if(n>0) {
    //         printf("read size %d\n",n);
    //         break;
    //     } else if(n==0) {
    //         printf("read size %d\n",n);
    //         break;
    //     } else {
    //         // 如果是非阻塞返回则等待1s再去读取
    //         if(errno==EWOULDBLOCK || errno==EAGAIN) {
    //             printf("sleep 1s\n");
    //             sleep(1);
    //         } else {
    //             perror("read");
    //             exit(1);
    //         }
    //     }
    // }
    // write(fd,buffer,n);

    while(true) {

        n=read(fd,buffer,sizeof(buffer)-1);
        if(~n) { // 读成功
            printf("read size %d\n",n);
            break;
        }
        if(errno!=EAGAIN) { // 读错误
            perror("read");
            exit(1);
        }
        // 非阻塞
        printf("sleep 1s\n");
        sleep(1);
    }
    write(STDOUT_FILENO,buffer,n);
}

void test_mycat(int argc,char *argv[]) {

    if(argc<2) {
        exit(1);
    }

    FILE * pf=fopen(argv[1],"r");
    assert(pf!=NULL);

    int ch='\0';
    while((ch=fgetc(pf))!=EOF) {
        fputc(ch,stdout);
    }
    putchar('\n');
}

void test_mycp(int argc,char *argv[]) {

    if(argc<3) {
        exit(1);
    }

    FILE * src=fopen(argv[1],"r");
    assert(src!=NULL);
    FILE * dst=fopen(argv[2],"w");
    assert(dst!=NULL);
    int ch='\0';
    while((ch=fgetc(src))!=EOF) {
        fputc(ch,dst);
    }

}

/*
    mmap可以把磁盘文件的一部分直接映射到内存，这样文件中的位置直接就有对应的内存地址，
    对文件的读写可以直接用指针来做而不需要read/write函数
*/
/*
 void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
*/
/*
int prot:
    PROT_EXEC  Pages may be executed.
    PROT_READ  Pages may be read.
    PROT_WRITE Pages may be written.
    PROT_NONE  Pages may not be accessed.
*/
// 将文件与内存进行映射,无需使用系统调用直接使用指针即可完成文件的读写
void test_mmap() {

#define GiB *( 1024LL * 1024 * 1024 )

    // // 申请3GB内存

    // void *p=mmap(NULL,3 GiB,PROT_READ|PROT_WRITE,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);
    // if(p==MAP_FAILED) {
    //     perror("mmap");
    //     exit(1);
    // }
    // // ...

    return;
   {
        int fd=open("./test.txt",O_RDWR);
        assert(fd>=0);

        char *ptr=(char*)mmap(NULL,10,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);

        for(int i=0;i<10;++i) {
            fputc(ptr[i],stdout);
        }
        fputc('\n',stdout);


        for(int i=0;i<10;++i) {
            ptr[i]='1'+i;
        }

        // int munmap(void *addr, size_t length);
        munmap(ptr,10);
        close(fd);
   }
}

/*
读取文件的inode,然后把inode中的各种文件属性填入一个struct stat结构体传出给调用者。

    int stat(const char *path, struct stat *buf);
    int fstat(int fd, struct stat *buf);
    int lstat(const char *path, struct stat *buf);
*/
// struct stat {
//     dev_t     st_dev;     /* ID of device containing file */
//     ino_t     st_ino;     /* inode number */
//     mode_t    st_mode;    /* protection */
//     nlink_t   st_nlink;   /* number of hard links */
//     uid_t     st_uid;     /* user ID of owner */
//     gid_t     st_gid;     /* group ID of owner */
//     dev_t     st_rdev;    /* device ID (if special file) */
//     off_t     st_size;    /* total size, in bytes */
//     blksize_t st_blksize; /* blocksize for file system I/O */
//     blkcnt_t  st_blocks;  /* number of 512B blocks allocated */
//     time_t    st_atime;   /* time of last access */
//     time_t    st_mtime;   /* time of last modification */
//     time_t    st_ctime;   /* time of last status change */
// };
void test_stat(int argc,char *argv[]) {

    if(argc<2) {
        exit(1);
    }

    struct stat st;
    // lstat与stat的区别如何传的是链接文件
    // lstat返回该链接文件的信息
    // stat返回该链接文件指向的源文件的信息
    if(lstat(argv[1],&st)<0) {
        perror("stat");
        exit(2);
    }

    /*
        S_ISREG(m)  is it a regular file?
        S_ISDIR(m)  directory?
        S_ISCHR(m)  character device?
        S_ISBLK(m)  block device?
        S_ISFIFO(m) FIFO (named pipe)?
        S_ISLNK(m)  symbolic link?  (Not in POSIX.1-1996.)
        S_ISSOCK(m) socket?  (Not in POSIX.1-1996.)

    */
    // 判断文件类型
    switch( st.st_mode&__S_IFMT ) {
        case __S_IFDIR: printf("directory\n"); break;
        case __S_IFREG: printf("is it a regular file\n"); break;
        case __S_IFIFO: printf("FIFO\n"); break;
        case __S_IFLNK: printf("symbolic link\n"); break;
        default:
            printf("other file\n");
            break;
    }

    // if(S_ISDIR(st.st_mode)) {
    //     printf("directory\n");
    // } else if(S_ISREG(st.st_mode)) {
    //     printf("is it a regular file\n");
    // } else if(S_ISFIFO(st.st_mode)) {
    //     printf("FIFO\n");
    // } else if(S_ISLNK(st.st_mode)) {
    //     printf("symbolic link\n");
    // } else {
    //     printf("other file\n");
    // }

}

void test_link_api() {
    // int link(const char *oldpath, const char *newpath);
    // 创建硬链接
    // link("./fifo","./fifo-h");
    // int symlink(const char *oldpath, const char *newpath);
    // 创建软链接
    // symlink("./fifo","./fifo-s");
    // int unlink(const char *pathname);
    // 删除文件
    // unlink("./temp");
    // int rename(const char *oldpath, const char *newpath);
    // 修改文件名称
    // rename("./temp","./test");
    // int mkdir(const char *pathname, mode_t mode);
    // 创建目录
    // mkdir("./a",0775);
    // ssize_t readlink(const char *path, char *buf, size_t bufsiz);

    // int fd=open("./test.txt-s",O_RDONLY);
    // char buff[1024];
    // ssize_t n=read(fd,buff,10); // 读test.txt的内容
    // for(int i=0;i<n;++i) {
    //     fputc(buff[i],stdout);
    // }

    // putchar('\n');

    // // readlink()将符号链接路径的内容放在缓冲区buf中
    // n=readlink("./test.txt-s",buff,10); // 读./test.txt-s的内容
    // for(int i=0;i<n;++i) {
    //     fputc(buff[i],stdout);
    // }

    // int rmdir(const char *pathname);
    // rmdir() deletes a directory, which must be empty.
    int ret=rmdir("./a");
    if(-1==ret) perror("rmdir");
}

// 实现类似ls -R的功能
void test_ls_r(const char *dirname) {

    // 获取对应目录的句柄
    DIR * pdir=opendir(dirname);
    if(NULL==pdir) {
        perror("opendir");
        exit(2);
    }

    // 遍历目录下的文件
    struct dirent *pdt=NULL;
    char buffer[1024];
    while((pdt=readdir(pdir))!=NULL) {

        printf("d_ino: %d | d_name: %s\n",pdt->d_ino,pdt->d_name);

        if(strcmp(".",pdt->d_name)==0 || strcmp("..",pdt->d_name)==0) continue; 

        // 根据dirname和pdt->name获取完整文件名
        memset(buffer,'\0',sizeof buffer);
        sprintf(buffer,"%s/%s",dirname,pdt->d_name);

        // 通过stat来获取文件的类型
        struct stat st;
        if(0==stat(buffer,&st)) {
            if(S_ISDIR(st.st_mode)) {
                test_ls_r(buffer);
            }
        }

    }

    closedir(pdir);
}

void test_dir(int argc,char *argv[]) {

    if(argc<2) {
        printf("cmd: exe + dirname\n");
        exit(1);
    }

    // DIR *opendir(const char *name);
    // DIR *fdopendir(int fd);
    DIR * pdir=opendir(argv[1]);
    if(NULL==pdir) {
        perror("opendir");
        exit(2);
    }
    // 用于遍历目录数据块中的记录。opendir打开一个目录，返回一个DIR *指针代表这个目录，它
    // 是一个类似FILE *指针的句柄，closedir用于关闭这个句柄，把DIR *指针传给readdir读取目
    // 录数据块中的记录，每次返回一个指向struct dirent的指针，反复读就可以遍历所有记录，所
    // 有记录遍历完之后readdir返回NULL。结构体struct dirent的定义如下:
    // struct dirent *readdir(DIR *dirp);
    // struct dirent {
    //     ino_t          d_ino;       /* inode number */
    //     off_t          d_off;       /* not an offset; see NOTES */
    //     unsigned short d_reclen;    /* length of this record */
    //     unsigned char  d_type;      /* type of file; not supported by all file system types */
    //     char           d_name[256]; /* filename */
    // };
    // struct dirent *dt=readdir(pdir);
    // if(NULL==pdir) {
    //     perror("readdir");
    //     exit(3);
    // }
    struct dirent *pdt=NULL;
    while((pdt=readdir(pdir))!=NULL) {
        printf("d_ino: %d | d_name: %s\n",pdt->d_ino,pdt->d_name);
    }

    // int closedir(DIR *dirp);
    closedir(pdir);
}

void test_dup() {
    // int dup(int oldfd);
    // int dup2(int oldfd, int newfd);    

    // dup: 复制oldfd文件描述符,返回最小没有使用的下标
    // int fd=dup(1);

    // char buff[1024];
    // memset(buff,'\0',sizeof buff);

    // strcpy(buff,"abcdef\n");
    // write(fd,buff,strlen(buff));
    // strcpy(buff,"xyz\n");
    // write(1,buff,strlen(buff));

    // open两次相同的文件与dup复制的区别

    // 1.
    int fd1=open("./test.txt",O_RDONLY);
    int fd2=open("./test.txt",O_RDONLY);

    char buff[128];

    int n=read(fd1,buff,3);
    n=read(fd2,buff+n,3);

    printf(buff);
    putchar(10);

    // 2.
    int fd3=open("./test.txt",O_RDONLY);
    int fd4=dup(fd3);
    
    n=read(fd3,buff,3);
    n=read(fd4,buff+n,3);

    printf(buff);
    putchar(10);
    // dup和dup2都可用来复制一个现存的文件描述符，使两个文件描述符指向同一个file结构体。如
    // 果两个文件描述符指向同一个file结构体，File Status Flag和读写位置只保存份在file结
    // 构体中，并且file结构体的引用计数是2。如果两次open同一文件得到两个文件描述符，则每个
    // 描述符对应一个不同的file结构体，可以有不同的File Status Flag和读写位置。
    // 输出:
    // abcabc
    // abcdef
    // 每一次open都会单独分配一个file结构体,里面的位置指针独有
    // 如果是dup则会与oldfd同用file结构体,里面的count计数+1
}

int main(int argc,char *argv[],char *env[]) {

    // test_argv(argc,argv);
    // test_env(env);
    // test_addresses(argc,argv,env);
    // test_proc();

    // test_stream();
    // test_read();
    // test_rule_for_distribution();
    // test_redirected();
    // test_dup2();
    // test_buffer();

    // test_block();
    // test_nonblock();
    // test_mycat(argc,argv);
    // test_mycp(argc,argv);

    // test_mmap();
    // test_stat(argc,argv);
    // test_link_api();
    // test_dir(argc,argv);
    // test_ls_r(argv[1]);
    test_dup();
    return 0;
}
